function pts = roi_pick_points(N, ax)
% 右键添加 N 个点；内置左键拖拽平移 + 滚轮缩放（以光标为中心）
% 兼容 figure/uifigure + axes/uiaxes（若 uifigure 不支持某些 Window* 回调，请告知版本）

    if nargin < 2 || isempty(ax), ax = gca; end
    if nargin < 1 || ~isscalar(N) || N <= 0, error('N 必须为正整数'); end

    fig = ancestor(ax, 'figure');
    hold(ax, 'on');

    % === 图像句柄（imshow/imshowpair 产生的 image） ===
    img = findobj(ax, 'Type', 'image');
    if isempty(img), error('请先在 ax 上用 imshow/imshowpair 显示图像'); end
    hImg = img(1);
    set(hImg, 'HitTest','on', 'PickableParts','all');

    % === 状态 ===
    pts  = nan(N, 2);
    rois = gobjects(N, 1);
    k    = 0;

    isPanning   = false;
    panLastXY   = [NaN NaN];  % 上次鼠标在轴坐标
    oldTitle    = get(get(ax,'Title'),'String');

    % 备份旧回调（避免影响你现有逻辑）
    oldWBD = get(fig,'WindowButtonDownFcn');
    oldWBM = get(fig,'WindowButtonMotionFcn');
    oldWBU = get(fig,'WindowButtonUpFcn');
    oldSWF = get(fig,'WindowScrollWheelFcn');
    oldWKP = get(fig,'WindowKeyPressFcn');
    oldUIM = get(hImg,'UIContextMenu');

    % 某些右键菜单会吞事件：临时移除
    set(hImg,'UIContextMenu',[]);

    % ——提示（可注释）——
    title(ax, '右键加点；左键拖拽=平移；滚轮=缩放；Backspace删；Enter结束');

    % ——我们自己接管滚轮、按键、鼠标按下/移动/抬起（仅这一窗口期间）——
    set(fig,'WindowScrollWheelFcn', @(src,evt) onScrollZoom(ax, evt));
    set(fig,'WindowKeyPressFcn',   @onKeyPress);
    set(fig,'WindowButtonDownFcn', @onMouseDown);
    set(fig,'WindowButtonMotionFcn',@onMouseMove);
    set(fig,'WindowButtonUpFcn',   @onMouseUp);

    % 阻塞等待（但 GUI 可交互）
    uiwait(fig);

    % ——清理与恢复——
    if isvalid(fig)
        try set(fig,'WindowButtonDownFcn', oldWBD); end
        try set(fig,'WindowButtonMotionFcn',oldWBM); end
        try set(fig,'WindowButtonUpFcn',   oldWBU); end
        try set(fig,'WindowScrollWheelFcn',oldSWF); end
        try set(fig,'WindowKeyPressFcn',   oldWKP); end
    end
    if isvalid(hImg)
        try set(hImg,'UIContextMenu', oldUIM); end
    end
    if isvalid(ax)
        try title(ax, oldTitle); end
    end

    % 输出有效点
    pts = pts(1:sum(all(isfinite(pts),2)), :);

    % ===== 回调实现 =====
    function onMouseDown(~, ~)
        % 命中测试：必须在目标轴内才响应
        hit = hittest(fig);
        hitAx = ancestor(hit, 'axes');
        if ~isequal(hitAx, ax)
            return;
        end

        switch fig.SelectionType
            case 'alt'  % 右键：加点
                if k >= N, return; end
                cp = ax.CurrentPoint(1,1:2);
                k = k + 1;
                rois(k) = drawpoint(ax,'Position',cp,'Deletable',true,'Label',sprintf('%d',k));
                pts(k,:) = cp;
                addlistener(rois(k),'ROIMoved',@(src,evt) onROIMoved(src,evt,k));
                if k >= N
                    try uiresume(fig); end
                end

            case 'normal'  % 左键：开始平移
                % 如果点在 ROI 上让 ROI 自己处理，避免抢拖拽
                if isgraphics(hit) && isprop(hit, 'Type')
                    if strcmpi(get(hit,'Type'),'images.roi.point') || ~isempty(ancestor(hit,'images.roi.Point'))
                        return; % ROI 自己处理拖拽
                    end
                end
                cp = ax.CurrentPoint(1,1:2);
                if isInsideAxes(ax, cp)
                    isPanning = true;
                    panLastXY = cp;
                end

            otherwise
                % 中键/双击等不处理
        end
    end

    function onMouseMove(~, ~)
        if ~isPanning, return; end
        cp = ax.CurrentPoint(1,1:2);
        if any(~isfinite(cp)) || any(~isfinite(panLastXY)), return; end

        % 计算位移（轴坐标）并移动视图
        d = cp - panLastXY;
        xl = xlim(ax); yl = ylim(ax);
        % 平移方向：拖动图像看起来跟手（这里选择图像随鼠标同向移动）
        xlim(ax, xl - d(1));
        ylim(ax, yl - d(2));

        panLastXY = cp;
    end

    function onMouseUp(~, ~)
        isPanning = false;
        panLastXY = [NaN NaN];
    end

    function onScrollZoom(axh, evt)
        if isempty(axh) || ~isvalid(axh), return; end
        cp = axh.CurrentPoint(1,1:2);
        if ~isInsideAxes(axh, cp), return; end

        % 缩放因子：滚轮上=放大，下=缩小
        step = 1.10;
        if evt.VerticalScrollCount > 0
            s = step;      % 缩小视野（看起来变小）
        else
            s = 1/step;    % 放大视野（看起来变大）
        end

        xl = xlim(axh); yl = ylim(axh);
        cx = cp(1); cy = cp(2);
        newXL = cx + (xl - cx) * s;
        newYL = cy + (yl - cy) * s;

        % 避免窗口太小导致抖动
        minW = max([diff(xl), diff(yl)]) * 1e-6;
        if diff(newXL) > minW && diff(newYL) > minW
            xlim(axh, newXL); ylim(axh, newYL);
        end
    end

    function onKeyPress(~, evt)
        switch lower(evt.Key)
            case {'return','enter'}
                try uiresume(fig); end
            case {'backspace','delete'}
                if k>0 && isvalid(rois(k))
                    try delete(rois(k)); end
                    pts(k,:) = [nan nan];
                    k = k - 1;
                end
        end
    end

    function onROIMoved(src, ~, idx)
        if isvalid(src)
            pts(idx,:) = src.Position;
        end
    end

    function tf = isInsideAxes(axh, pt)
        xl = xlim(axh); yl = ylim(axh);
        tf = pt(1) >= min(xl) && pt(1) <= max(xl) && ...
             pt(2) >= min(yl) && pt(2) <= max(yl);
    end
end
